package com.baturu.mybatisminus.util;

import com.baturu.mybatisminus.MinusConfiguration;
import com.baturu.mybatisminus.annotation.info.ColumnAnnotation;
import com.baturu.mybatisminus.annotation.info.IdAnnotation;
import com.baturu.mybatisminus.annotation.info.TableAnnotation;
import com.baturu.mybatisminus.annotation.info.TransientAnnotation;
import com.baturu.mybatisminus.model.FieldInfo;
import com.baturu.mybatisminus.model.TableInfo;
import org.apache.ibatis.builder.MapperBuilderAssistant;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author huangbin
 */
public class EntityParseUtils {

    private static Map<Class<?>, List<Field>> classToFieldMap = new ConcurrentHashMap<>();

    public static TableInfo parseEntityClass(MapperBuilderAssistant builderAssistant, Class<?> entityClass, Class<?> idClass) {
        TableInfo tableInfo = parseTable(builderAssistant, entityClass, idClass);
        parseId(builderAssistant, entityClass, tableInfo);
        parseFields(builderAssistant, entityClass, tableInfo);
        return tableInfo;
    }


    private static TableInfo parseTable(MapperBuilderAssistant builderAssistant, Class<?> entityClass, Class<?> idClass) {
        MinusConfiguration configuration = (MinusConfiguration) builderAssistant.getConfiguration();

        TableInfo tableInfo = new TableInfo();
        tableInfo.setEntityClass(entityClass);
        tableInfo.setIdClass(idClass);
        tableInfo.setCurrentNamespace(builderAssistant.getCurrentNamespace());
        TableAnnotation table = AnnotationUtils.parseTableAnnotation(entityClass);
        String tableName = entityClass.getSimpleName();
        if (table != null && StringUtils.isNotEmpty(table.value())) {
            tableName = table.value();
        } else {
            if (configuration.getTableNameUnderLine()) {
                tableName = StringUtils.camelToUnderline(tableName);
            }
        }
        tableInfo.setTableName(tableName);
        tableInfo.setEscapedTableName(SqlReservedWords.escapeIfNecessary(tableName));
        return tableInfo;
    }

    private static void parseId(MapperBuilderAssistant builderAssistant, Class<?> clazz, TableInfo tableInfo) {
        MinusConfiguration configuration = (MinusConfiguration) builderAssistant.getConfiguration();

        for (Field field : getValidFields(clazz)) {
            if (isIdField(field)) {
                String idColumn = field.getName();
                ColumnAnnotation column = AnnotationUtils.parseColumnAnnotation(field);
                if (column != null && StringUtils.isNotEmpty(column.value())) {
                    idColumn = column.value();
                } else {
                    if (configuration.getColumnNameUnderLine()) {
                        idColumn = StringUtils.camelToUnderline(idColumn);
                    }
                }
                tableInfo.setIdProperty(field.getName());
                tableInfo.setIdColumn(idColumn);
                tableInfo.setEscapedIdProperty(SqlReservedWords.escapeIfNecessary(field.getName()));
                tableInfo.setEscapedIdColumn(SqlReservedWords.escapeIfNecessary(idColumn));
                break;
            }
        }
        if (StringUtils.isEmpty(tableInfo.getIdProperty())) {
            throw new IllegalStateException(clazz.getName() + "中找不到id字段，请确保实体类中存在字段名为id或@Id注解的字段");
        }
    }

    private static void parseFields(MapperBuilderAssistant builderAssistant, Class<?> clazz, TableInfo tableInfo) {
        MinusConfiguration configuration = (MinusConfiguration) builderAssistant.getConfiguration();

        List<FieldInfo> fieldInfoList = new ArrayList<>();
        for (Field field : getValidFields(clazz)) {
            FieldInfo fieldInfo = FieldParseUtils.parseField(field, configuration);
            fieldInfoList.add(fieldInfo);
        }
        tableInfo.setFieldList(fieldInfoList);

        List<FieldInfo> insertableFieldList = fieldInfoList.stream()
                .filter(FieldInfo::isInsertable)
                .filter(field -> !field.getProperty().equals(tableInfo.getIdProperty()))
                .collect(Collectors.toList());
        tableInfo.setInsertableFieldList(insertableFieldList);

        List<FieldInfo> updatableFieldList = fieldInfoList.stream()
                .filter(FieldInfo::isUpdatable)
                .filter(field -> !field.getProperty().equals(tableInfo.getIdProperty()))
                .collect(Collectors.toList());
        tableInfo.setUpdatableFieldList(updatableFieldList);

        Set<String> propertySet = fieldInfoList.stream()
                .map(FieldInfo::getProperty)
                .collect(Collectors.toSet());
        tableInfo.setPropertySet(propertySet);

        Map<String, FieldInfo> propertyToFieldInfoMap = fieldInfoList.stream()
                .collect(Collectors.toMap(FieldInfo::getProperty, Function.identity(), (a, b) -> a));
        tableInfo.setPropertyToFieldInfoMap(propertyToFieldInfoMap);
    }


    private static boolean isIdField(Field field) {
        return field.getName().equals("id") || isIdAnnotationPresent(field);
    }


    private static boolean isIdAnnotationPresent(Field field) {
        IdAnnotation id = AnnotationUtils.parseIdAnnotation(field);
        return id != null;
    }


    private static List<Field> getValidFields(Class<?> clazz) {
        return classToFieldMap.computeIfAbsent(clazz, cls -> {
            List<Field> fieldList = ReflectionUtils.getFieldList(ClassUtils.getUserClass(clazz));
            if (CollectionUtils.isNotEmpty(fieldList)) {
                Iterator<Field> iterator = fieldList.iterator();
                while (iterator.hasNext()) {
                    Field field = iterator.next();
                    TransientAnnotation silent = AnnotationUtils.parseTransientAnnotation(field);
                    if (silent != null) {
                        iterator.remove();
                    }
                }
            }
            if (CollectionUtils.isEmpty(fieldList)) {
                throw new IllegalStateException(clazz.getName() + "类中不存在有效的字段");
            }
            return fieldList;
        });
    }


}
